package ph.com.isla.sf2d;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;

import ph.com.isla.sf2d.ui.SF2DDialog;
import ph.com.isla.sf2d.ui.SF2DObject;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class SF2DSurfaceView extends SurfaceView implements
		SurfaceHolder.Callback {
	private static int instanceCtr = 0;
	private volatile List<SF2DObject> mObjects;
	private volatile List<SF2DSprite> mSprites;
	private volatile List<TouchEventCallback> mTouchEventCallbackList;
	private volatile ThreadGroup mThreadGroup = null;
	private int mSurfaceWidth;
	private int mSurfaceHeight;
	private float mScaleX;
	private float mScaleY;
	private boolean mAttached;
	private boolean mInGameLoop;
	private DrawThread mDrawThread;
	
	public int getScaledWidth() {
		return mSurfaceWidth;
	}
	
	public int getScaledHeight() {
		return mSurfaceHeight;
	}
	
	
	
	public  void putSF2DObjectOnTop(SF2DObject object){
		synchronized(mObjects){
			mObjects.remove(object);
			mObjects.add(object);
		}
	}
	
	public void removeSF2DObject(SF2DObject object){
		synchronized(mObjects){
			mObjects.remove(object);
		}
	}

	public float getScaleX() {
		return mScaleX;
	}

	public float getScaleY() {
		return mScaleY;
	}

	public void setInGameLoop(boolean value) {
		mInGameLoop = value;
	}

	@Override
	public void postInvalidate() {
		mDrawThread.resumeNow();
	}

	public boolean isInGameLoop() {
		return mInGameLoop;
	}

	public List<SF2DSprite> getSprites() {
		return mSprites;
	}

	public SF2DSurfaceView(Context context, int surfaceWidth, int surfaceHeight) {
		super(context);
		setWillNotDraw(true);
		this.getHolder().addCallback(this);
		this.mSurfaceHeight = surfaceHeight;
		this.mSurfaceWidth = surfaceWidth;
		initialize();
		Log.i("SFL", "  onCreate");
	}

	public void addSprite(SF2DSprite sprite) {
		synchronized (mSprites) {
			mSprites.add(sprite);
			sprite.setSurface(this);
		}
		if ((isAttached()) && (!isInGameLoop())) {
			postInvalidate();
		}
	}

	@Override
	public void onAttachedToWindow() {
		Log.i("SFL", "onAttached!");
		super.onAttachedToWindow();
		mAttached = true;
	}

	/**
	 * 
	 * @return true if this Surface is already attached to a window
	 */
	public boolean isAttached() {
		return mAttached;
	}

	private void initialize() {
		instanceCtr++;
		mSprites = new ArrayList<SF2DSprite>();
		mObjects = new ArrayList<SF2DObject>();
		mThreadGroup = new ThreadGroup(SF2DSurfaceView.class.getName()
				+ instanceCtr);
		mTouchEventCallbackList = new ArrayList<TouchEventCallback>();
	}

	public int addSF2DObject(final SF2DObject object, int left, int top) {
		int ret = -1;
		if (object != null) {
			object.setTop(top);
			object.setLeft(left);
			synchronized (mObjects) {
				mObjects.add(object);
				ret = mObjects.size() - 1;
			}

			if (isAttached()) {
				postInvalidate();
			}
		}
		return ret;
	}
	
	public int addSF2DDialog(final SF2DDialog object, int left, int top) {
		int ret = -1;
		if (object != null) {
			object.setTop(top);
			object.setLeft(left);
			synchronized (mObjects) {
				mObjects.add(object);
				ret = mObjects.size() - 1;
			}

		}
		return ret;
	}

	private volatile Object mObjectDown = null;

	@Override
	public boolean onTouchEvent(final MotionEvent event) {

		SF2DSprite sprite = null;
		int action = event.getAction();
		float x = event.getX() / mScaleX;
		float y = event.getY() / mScaleY;

		// check if there's an object hit
		if (action == MotionEvent.ACTION_DOWN) {
			mObjectDown = getClickableSFLObjectAt(x, y);
			if (mObjectDown == null) {
				mObjectDown = getClickableSFLSpriteAt(x, y);
				((SF2DSprite) mObjectDown)
						.setStatus(SF2DSpriteInterface.Status.PRESSED_DOWN);
				if (((SF2DSprite) mObjectDown).isDraggable()) {
					synchronized (mSprites) {
						mSprites.remove(mObjectDown);
						mSprites.add((SF2DSprite) mObjectDown);
					}
				}
			} else
				((SF2DObject) mObjectDown).onKeyDown(x, y);

			if (mObjectDown == null)
				return false;
		} else {
			if (mObjectDown instanceof SF2DSprite) {
				switch (action) {
				case MotionEvent.ACTION_UP:
					Object objectUp = getClickableSFLSpriteAt(x, y);

					if (mObjectDown == objectUp) {

						sprite = ((SF2DSprite) mObjectDown);
						if (sprite.getStatus() == SF2DSpriteInterface.Status.DRAG_STARTED) {
							sprite.moveCenter(x, y);
							sprite.setStatus(SF2DSpriteInterface.Status.DROPPED);
							SF2DSpriteInterface.OnDragListener listener = sprite
									.getOnDragListener();
							if (listener != null)
								listener.onDrop(sprite);
							postInvalidate();

						} else if (sprite.getStatus() == SF2DSpriteInterface.Status.PRESSED_DOWN) {

							sprite.setStatus(SF2DSpriteInterface.Status.CLICKED);
							sprite.onClick(sprite);
						}

					}
					break;

				case MotionEvent.ACTION_MOVE: {
					final SF2DSprite spriteOnMove = ((SF2DSprite) mObjectDown);
					if (spriteOnMove.isDraggable()) {
						new Thread(new Runnable() {
							@Override
							public void run() {
								synchronized (spriteOnMove) {
									spriteOnMove.moveCenter(event.getX()
											/ mScaleX, event.getY() / mScaleY);
									spriteOnMove
											.setStatus(SF2DSpriteInterface.Status.DRAGGED);
								}

							}
						}).start();

						postInvalidate();
					}
					break;
				}
				}
			} else {
				switch (action) {
				case MotionEvent.ACTION_UP:
					Object objectUp = getClickableSFLObjectAt(x, y);
					if (mObjectDown == objectUp) {
						SF2DObject obj = (SF2DObject) mObjectDown;
						obj.onKeyUp(x, y);
					}
					break;

				case MotionEvent.ACTION_MOVE:

					SF2DObject objDown = (SF2DObject) mObjectDown;
					Object objMove = getClickableSFLObjectAt(x, y);
					if (objMove == null)
						objDown.onMoveOut(x, y);
					else {
						if (objMove != objDown)
							objDown.onMoveOut(x, y);
					}
				}
			}
		}

		return true;

	}

	public SF2DSprite getClickableSFLSpriteAt(float x, float y) {
		synchronized (mSprites) {
			ListIterator<SF2DSprite> iterator = mSprites.listIterator(mSprites
					.size());
			while (iterator.hasPrevious()) {
				final SF2DSprite sprite = iterator.previous();
				if (sprite.isClickable()) {

					if (sprite.getRectF().contains(x, y)) {
						return sprite;
					}
				}
			}
		}

		Log.i("SFL", "returning null sprite!");
		return null;
	}

	public SF2DObject getClickableSFLObjectAt(float x, float y) {
		synchronized (mObjects) {
			ListIterator<SF2DObject> iterator = mObjects.listIterator(mObjects
					.size());
			while (iterator.hasPrevious()) {
				final SF2DObject object = iterator.previous();
				if (object.isClickable()) {

					if (object.getClickableArea().contains((int) x, (int)y)) {
						return object;
					}
				}
			}
		}

		Log.i("SFL", "returning null object!");
		return null;
	}

	public static interface TouchEventCallback {
		public void onTouchEvent(SurfaceView view, MotionEvent event);

		public RectF getTouchBoundary();
	}

	/**
	 * Add a callback for touch event on the SurfaceView
	 * 
	 * @param callback
	 *            object that will be invoked when touch event happens
	 */
	public void addTouchEventCallback(TouchEventCallback callback) {
		synchronized (mTouchEventCallbackList) {
			mTouchEventCallbackList.add(callback);
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {

		Log.i("SFL", "  surfaceChanged width [" + width + "]  height ["
				+ height + "]");
		// compute the scale here
		mScaleX = (float) width / (float) mSurfaceWidth;
		mScaleY = (float) height / (float) mSurfaceHeight;
		// setWillNotDraw(false);
		mDrawThread.start();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		Log.i("SFL", "  surfaceCreated");
		mDrawThread = new DrawThread(holder, false);
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) {
		Log.d("SFL", "surface destroy");
		boolean retry = true;
		mDrawThread.exitNow();
		while (retry) {
			try {
				mDrawThread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}

	private class DrawThread extends Thread implements Runnable {
		SurfaceHolder mHolder;
		volatile boolean mExit;
		boolean mGameLoop;

		public DrawThread(SurfaceHolder holder, boolean gameLoop) {
			super(DrawThread.class.getSimpleName());
			mHolder = holder;
			mGameLoop = gameLoop;
		}

		@Override
		public void run() {
			while (!mExit) {
				Log.i("SFL", "Loopinggg!!! " + new Date());
				Canvas c = null;
				try {
					c = mHolder.lockCanvas(null);
					synchronized (mHolder) {
						doDraw(c);
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mHolder.unlockCanvasAndPost(c);
					}
				}
				if (!mGameLoop) {
					pauseNow();
				}
			}

			Log.i("SFL", "Exit draw thread now!");
		}

		public void pauseNow() {
			try {
				synchronized (this) {
					wait();
				}
			} catch (Exception e) {

			}
		}

		public void resumeNow() {
			try {
				synchronized (this) {
					notify();
				}
			} catch (Exception e) {
			}
		}

		public void exitNow() {
			mExit = true;
			resumeNow();

		}

		public void doDraw(Canvas canvas) {
			canvas.scale(mScaleX, mScaleY);
			synchronized (mSprites) {
				for (final SF2DSprite sprite : mSprites) {
					Log.i("SFL", "sprite " + sprite.getId() + " drawn");
					sprite.draw(canvas);
				}
			}

			synchronized (mObjects) {
				for (SF2DObject object : mObjects) {
					if (!object.isVisible())
						continue;
					
					
					Log.i("SFL", "object " + object.getId() + " drawn");
					object.draw(canvas);
				}
			}
		}
	}
	
	public boolean isDialogDismissed(){

		synchronized (mObjects) {
			ListIterator<SF2DObject> iterator = mObjects.listIterator(mObjects
					.size());
			while (iterator.hasPrevious()) {
				final SF2DObject object = iterator.previous();
				if (object instanceof SF2DDialog){
					
					if (object.isVisible()){
						((SF2DDialog) object).dismiss();
						return true;
					} else 
						return false;
				}
				
			}
		}
		return false;
	}

}
